/// ===========================================================================
/// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
/// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
/// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
/// PURPOSE.
/// ===========================================================================
/// 
/// Project:        MOSS Faceted Search
/// Author:         Leonid Lyublinski (leonidly@microsoft.com)
/// Company:        Microsoft Services
/// Date:           05/04/2007  Version:        1.0
///
/// ===========================================================================

using System;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Xml.Serialization;
using System.Data;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.Office.Server;
using Microsoft.Office.Server.Search.Query;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

namespace Microsoft.SharePoint.Portal.ExtendedSearch.WebControls
{
    /// <summary>
    /// Contains utilities to parse/build querystring. Implemented as static methods.
    /// </summary>
    /// <remarks>Implementation of the faceted search relies on </remarks>
    internal class Utility
    {
        /// <summary>
        /// Creates SearchQuery structure from the querystring
        /// </summary>
        /// <returns><seealso cref="SearchQuery"/></returns>
        /// <remarks>Parsing of querystring is based on regular expressions patterns</remarks>
        public static SearchQuery ExtractQuery()
        {
            SearchQuery query = new SearchQuery();
            query.Properties = new NameValueCollection();

            if (HttpContext.Current.Request.QueryString == null) return query;

            // k parameter
            string k = HttpContext.Current.Request.QueryString["k"];
            if (!string.IsNullOrEmpty(k))
            {
                // separate search word from properties by space, assuming that keyphrase is 1st
                // look for property by construct <property name>:"<property value>", array of properties should look like
                // xxx: yyy: zzz:.
                // search phrase can be quoted and have a space. Space is a delimiter, yet will look positively fwd
                // skip quoted phrases
                int propertyStart = -1;  //                             -------VVV-------- addition
                string propertyPattern = @"(?<propName>(\w+)):""(?<propValue>([^""]+))""";
                Regex propertyRe = new Regex(propertyPattern, RegexOptions.IgnoreCase);
                try
                {
                    MatchCollection propertyMatches = propertyRe.Matches(k);
                    if (propertyMatches.Count > 0)
                    {
                        propertyStart = propertyMatches[0].Index;

                        foreach (Match property in propertyMatches)
                        {
                            CaptureCollection captures = property.Captures;
                            string propName = property.Groups["propName"].Value;
                            string propValue = property.Groups["propValue"].Value;
                            query.Properties.Add(propName, propValue);
                        }

                        query.Keywords = (propertyStart > 0) ? k.Substring(0, propertyStart-1).Trim() : string.Empty;
                    }
                    else 
                        query.Keywords = k.Trim();
                    
                }
                catch { }
            }

            // s parameter
            query.Scope = HttpContext.Current.Request.QueryString["s"];

            // v parameter
            query.View = HttpContext.Current.Request.QueryString["v"];

            // start parameter
            string start = HttpContext.Current.Request.QueryString["start"];
            query.Start = (string.IsNullOrEmpty(start) ? -1 : int.Parse(start));


            return query;
        }

        /// <summary>
        /// Builds a querystring by joining META properties with keyword, view, scope and start parameters.
        /// </summary>
        /// <param name="query"><see cref="SearchQuery"/></param>
        /// <returns>Returns a generated querystring <see cref="string"/> to use in response redirect</returns>
        public static string BuildQueryString(SearchQuery query)
        {
            string qs = string.Empty;
            ArrayList qsa = new ArrayList();
            if (!string.IsNullOrEmpty(query.Scope))
                qsa.Add("s=" + HttpUtility.UrlEncode(query.Scope));            
            if (!string.IsNullOrEmpty(query.View))
                qsa.Add("v=" + HttpUtility.UrlEncode(query.View));   
            if (query.Start > 0)
                qsa.Add("start=" + HttpUtility.UrlEncode(query.Start.ToString()));

            ArrayList ka = new ArrayList();
            if (!string.IsNullOrEmpty(query.Keywords))
                ka.Add(query.Keywords);
            foreach (string property in query.Properties)
            {
                // consider empty facets and don't add them to qs
                if (query.Properties[property] == Common.Constants.UNNAMED_FACET_VALUE) continue;
                ka.Add(string.Format("{0}:\"{1}\"",property, query.Properties[property]));
            }
            if (ka.Count > 0)
            {
                string ks = string.Join(" ", (string[]) ka.ToArray(typeof(string)));
                qsa.Add("k=" + HttpUtility.UrlEncode(ks));

            }
            if (qsa.Count>0)
                qs = string.Join("&", (string[]) qsa.ToArray(typeof(string)));

            return qs;
        }

        public static bool IsMatch(string pattern, string value)
        {
            if (string.IsNullOrEmpty(pattern) || string.IsNullOrEmpty(value)) 
                return false;
            if (pattern.IndexOf('%') != (pattern.Length - 1)) 
                return (pattern.ToLower() == value.ToLower());  // exact match
            else
                return value.ToLower().StartsWith(pattern.ToLower().TrimEnd('%'));    // starts-with match
        }

    }

    /// <summary>
    /// Contains methods to build Sql statement
    /// </summary>
    /// <remarks>Implementation of the faceted search relies on </remarks>
    internal class SqlBuilder
    {
        #region Fields
        private string _scope;
        private string _k;
        private string _hiddenRule;
        private NameValueCollection _properties;
        StringCollection _columns;
        private bool prefixEnabled;
        #endregion

        #region Constructors
        public SqlBuilder(SearchQuery query, StringCollection columns)
        {
            _scope = query.Scope;
            _k = query.Keywords;
            _properties = query.Properties;
            _columns = columns;
        }
        public SqlBuilder(SearchQuery query, StringCollection columns, bool useContains)
        {
            _scope = query.Scope;
            _k = query.Keywords;
            _properties = query.Properties;
            _columns = columns;
            prefixEnabled = useContains;
        }
        #endregion

        #region Properties

        /// <summary>
        /// Constructs final SQL statement from the querystring
        /// </summary>
        public string SqlQuery
        {
            get
            {
                string sql = GetSELECT();
                sql += GetFROM();
                sql += GetWHERE();
                sql += GetORDERBY();
                return sql;
            }
        } 
        #endregion

        #region Methods
        /// <summary>
        /// Constructs SELECT SQL statement from the querystring based on defined 
        /// facets through the WP UI.
        /// </summary>
        /// <returns></returns>
        private string GetSELECT()
        {
            string select = " SELECT Path ";
            string columns = GetParsedColumns();
            if (!columns.Equals(string.Empty))
                select = select + ", " + columns;

            return select;
        }

        private string GetFROM()
        {
            return " FROM scope() ";
        }

        private string GetWHERE()
        {
            ArrayList where = new ArrayList();
            if (!string.IsNullOrEmpty(_k))
            {
                string searchFor;
                if (_k.StartsWith("\"") && _k.EndsWith("\""))
                {
                    searchFor = _k.TrimEnd('"').TrimStart('"');
                }
                else
                {
                    Regex re = new Regex(@"\s+");
                    searchFor = re.Replace(_k, "\" AND \"");
                }
                //where.Add(" CONTAINS('\"" + _k + "*\"') "); // doesn't conform core results totals
                where.Add(" CONTAINS('\"" + searchFor + "\"') ");
            }
            if (!string.IsNullOrEmpty(_scope))
                where.Add("\"scope\"='" + _scope + "' ");

            for (int i = 0; i < _properties.Count; i++)
            {
                string propName = _properties.Keys[i];
                string propValue = _properties[propName].TrimEnd('%');
                //where.Add(" CONTAINS(\"" + propName + "\", '\"" + propValue + "\"') "); // no wild card here
                if (prefixEnabled)
                    where.Add(" \"" + propName + "\" LIKE '%" + propValue + "%' "); // wild card added to match core results
                else
                    where.Add(" \"" + propName + "\" LIKE '" + propValue + "%' "); // wild card added to match core results
            }

            if (where.Count > 0)
                return " WHERE " + string.Join(" AND ", (string[])where.ToArray(typeof(string)));
            return
                string.Empty;
        }

        private string GetORDERBY()
        {
            string orderby = " ORDER BY \"Rank\" DESC ";
            string columns = GetParsedColumns();
            if (!columns.Equals(string.Empty))
                orderby = orderby + ", " + columns;
            return String.Empty; //for now
        }


        /// <summary>
        /// Returns quoted delimited string of columns, used in building SQL
        /// </summary>
        /// <returns></returns>
        private string GetParsedColumns()
        {
            string columnsStr = string.Empty;
            if (_columns.Count > 0)
            {
                ArrayList al = new ArrayList(_columns);
                columnsStr = String.Join("\", \"", (string[])al.ToArray(typeof(string)));
                columnsStr = "\"" + columnsStr + "\"";
            }
            return columnsStr;
        } 
        #endregion
    }

    /// <summary>
    /// The structure holds search parameters, parsed from querystring and intended to build a querystring. 
    /// </summary>
    /// <example>Search string example: chicken Product:"Bisquick" Method:"bake"</example>
    public struct SearchQuery
    {
        /// <summary>
        /// Referes to the extract from keyword phrase. "k" in Url
        /// </summary>
        public string Keywords;
        /// <summary>
        /// Refers to the start page. "s" in Url
        /// </summary>
        public int Start;
        /// <summary>
        /// Refers to the view. "v" in Url
        /// </summary>
        public string View;
        /// <summary>
        /// Refers to the scope. "scope" in Url
        /// </summary>
        public string Scope;
        /// <summary>
        /// Referes to the extract from keyword phrase related to the META property. [facet name]:"[facet value]"
        /// </summary>
        public NameValueCollection Properties;

        /// <summary>
        /// Returns a shallow copy of the struct
        /// </summary>
        /// <returns></returns>
        public SearchQuery Clone()
        {
            SearchQuery query = this;
            query.Properties = new NameValueCollection(Properties);
            return query;
        }
    }
}
